/*
 * iSGL3D: http://isgl3d.com
 *
 * Copyright (c) 2010-2011 Stuart Caunt
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 *
 */

#import "Isgl3dTerrainMeshRandom.h"
#import "Isgl3dFloatArray.h"
#import "Isgl3dUShortArray.h"
#import "Isgl3dLog.h"
#import <CoreGraphics/CoreGraphics.h>
#import <UIKit/UIKit.h>

#include "PerlinNoise2.h"

@interface Isgl3dTerrainMeshRandom (PrivateMethods) 

@end

static PerlinNoise *perlin = new PerlinNoise(3.423,0.0009,918.568, 1, 3);

@implementation Isgl3dTerrainMeshRandom

+ (id) create:(float)width depth:(float)depth  nx:(int)nx nz:(int)nz worldx:(float)x worldz:(float)z{
    return [[[self alloc] initTerrain:width depth:depth nx:nx nz:nz worldx:x worldz:z] autorelease];
}

- (id) initTerrain:(float)width depth:(float)depth  nx:(int)nx nz:(int)nz worldx:(float)x worldz:(float)z{
	if ((self = [super init])) {
		
		_width = width;
		_depth = depth;

		_nx = nx;
		_nz = nz;
        
        wx = x;
        wz = z;
		
		[self constructVBOData];
	}
	
	return self;
}

- (void) dealloc {
    free (terrainHeightData);
    free (terrainNormalData);
    
    [super dealloc];
}


- (void) fillVertexData:(Isgl3dFloatArray *)vertexData andIndices:(Isgl3dUShortArray *)indices {	
    // Create array of heights
	unsigned int heightDataNx = _nx + 1;
	unsigned int heightDataNz = _nz + 1;
	_dx = _width / _nx;
	_dz = _depth / _nz;
    
    
	float perlinFix = 0.5;
    //height map
    terrainHeightData = (float*)malloc(heightDataNx * heightDataNz * sizeof(float));
    //and normal
    terrainNormalData = (float*)malloc(heightDataNx * heightDataNz * sizeof(float) * 3);
    
    for (int z=0; z<heightDataNz; z++) {
        for (int x=0; x<heightDataNx; x++) {
            float wwx = wx+_dx*x, wwz = wz+_dz*z;
            float height = perlin->GetHeight((wwx+perlinFix)/2.14159, (wwz+perlinFix)/2.14159);
            
            terrainHeightData[z*heightDataNx + x] = height;
        }
    }
    
	// Iterate again, this time calculating normal values
    int normalIndex = 0;
	for (int i = 0; i <= _nx; i++) {
		//float x = -(_width / 2) + i * (_width / _nx);
		float x = i * _dx;
        float u = i / _nx * 1.0;          // you can scale it by multiple a factor.
        
		for (int j = 0; j <= _nz; j++) {
			//float z = -(_depth / 2) + j * (_depth / _nz);
            float z = j * _dz;
			float v = j / _nz * 1.0;	
            
			// get height from terrain height data array
			float y = terrainHeightData[j * heightDataNx + i]; 		
            
			[vertexData add:x];
			[vertexData add:y];
			[vertexData add:z];
            
			// Calculate normal data (simple linear centered difference calculation)
			float hi0;
			float hi1;
			float hj0;
			float hj1;
			if (i > 0) {
				hi0 = terrainHeightData[j * heightDataNx + i - 1];
			} else {
				hi0 = y;
			}
			if (i < _nx) {
				hi1 = terrainHeightData[j * heightDataNx + i + 1];
			} else {
				hi1 = y;
			}
			if (j > 0) {
				hj0 = terrainHeightData[(j - 1) * heightDataNx + i];
			} else {
				hj0 = y;
			}
			if (j < _nz) {
				hj1 = terrainHeightData[(j + 1) * heightDataNx + i];
			} else {
				hj1 = y;
			}
            
			float nx = hi0 - hi1;
			float ny = 2 * (_dx + _dz);
			float nz = hj0 - hj1;
			float nLength = sqrt(nx * nx + ny * ny + nz * nz);
			nx = nx / nLength;
			ny = ny / nLength;
			nz = nz / nLength;
			
			[vertexData add:nx];
			[vertexData add:ny];
			[vertexData add:nz];
            
			[vertexData add:u];
			[vertexData add:v];
            
            terrainNormalData[normalIndex++] = nx;
            terrainNormalData[normalIndex++] = ny;
            terrainNormalData[normalIndex++] = nz;
		}
	}
	
	for (int i = 0; i < _nx; i++) {
		for (int j = 0; j < _nz; j++) {
			
			int first = i * (_nz + 1) + j;			
			int second = first + (_nz + 1);
			int third = first + 1;
			int fourth = second + 1;
			
			[indices add:first];
			[indices add:third];
			[indices add:second];
            
			[indices add:third];
			[indices add:fourth];
			[indices add:second];
		}
	}
  
}

// Point - Normal formula.
- (float) getHeight:(float)x z:(float)z{
    int x0 = x / _dx;
    int z0 = z / _dz;
    int i = z0*(_nz+1) + x0;
    float y0 = terrainHeightData[i];
    
    return y0;
    
    /*i = (x0*(_nx+1) + z0)*3;
    float A = terrainNormalData[i];
    float B = terrainNormalData[i+1];
    float C = terrainNormalData[i+2];
    
    float a = A*(x-x0), c = C*(z-z0);
    
    return (a+c) / (-B) + y0;*/
}
@end
